package com.example.starter.webmvc.handler;

import cn.hutool.core.util.ObjUtil;
import com.example.starter.exception.BusinessException;
import com.example.starter.util.Response;
import com.example.starter.webmvc.configure.WebMvcProperties;
import com.example.starter.webmvc.constant.ExceptionEnum;
import com.example.starter.webmvc.util.WebUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.security.SignatureException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * <p>
 * 统一异常处理
 * </p>
 *
 * @author 王令
 * @since 2022-05-13 15:14:14
 */
@Order
@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice
public class GlobalExceptionHandler<T> implements ResponseBodyAdvice<Response<T>> {

    private final WebMvcProperties properties;

    /**
     * 判断是否要执行beforeBodyWrite方法，true为执行，false不执行
     */
    @Override
    public boolean supports(MethodParameter returnType,
            @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return Response.class.isAssignableFrom(Objects.requireNonNull(returnType.getMethod()).getReturnType());
    }

    /**
     * 对response处理的执行方法
     */
    @Override
    public Response<T> beforeBodyWrite(Response<T> body,
            @NonNull MethodParameter returnType,
            @NonNull MediaType mediaType,
            @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
            @NonNull ServerHttpRequest request,
            @NonNull ServerHttpResponse response) {
        // 返回数据处理 ，内部请求不处理
        if (ObjUtil.isNotNull(body) && !WebUtil.isInner()) {
            body.setTimestamp(System.currentTimeMillis());
            // 响应码处理
            response.setStatusCode(body.resolveCode());
        }
        return body;
    }


    @ExceptionHandler(Exception.class)
    public Response<String> exception(Exception e) {
        printError(e);
        if (properties.getError().getThrowable()) {
            return Response.error(e.getLocalizedMessage());
        } else {
            return Response.error(properties.getError().getErrMsg());
        }
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Response<String> methodNotAllowed(HttpRequestMethodNotSupportedException e) {
        printError(e);
        return Response.error(ExceptionEnum.METHOD_NOT_ALLOWED);
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public Response<String> notFound404(NoHandlerFoundException e) {
        printError(e);
        return Response.fail(ExceptionEnum.NOT_FOUND_404);
    }

    @ExceptionHandler(BindException.class)
    public Response<List<String>> bindException(BindException e) {
        printWarn(e);
        return Response.fail(ExceptionEnum.BIND_EXCEPTION.getCode(),
                ExceptionEnum.BIND_EXCEPTION.getMessage() + ": " +
                        e.getBindingResult().getAllErrors().stream()
                                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                                .collect(Collectors.joining(";")));
    }

    @ExceptionHandler({
            BusinessException.class
    })
    public Response<String> businessException(BusinessException e) {
        printWarn(e);
        return Response.fail(e);
    }

    @ExceptionHandler({
            SignatureException.class,
            IllegalArgumentException.class
    })
    public Response<String> runtimeException(Exception e) {
        printWarn(e);
        return Response.fail(e.getLocalizedMessage());
    }

    private void printError(Exception e) {
        e.printStackTrace();
        log.error("{}", e.getLocalizedMessage());
    }

    private void printWarn(Exception e) {
        log.warn("{}", e.getLocalizedMessage());
    }

}
